package org.pixelami.twisted.spread.jelly
{
	import org.pixelami.twisted.utils.PyTuple;
	
	public class _Jellier
	{
		/*
		(Internal) This class manages state for a call to jelly()
	    */
	    
	    /*
	    private var constantTypes = {types.StringType : 1, types.IntType : 1,
	                     types.FloatType : 1, types.LongType : 1}
		
		*/
		
		
		public var taster:Object;
		public var persistentStore:Object;
		public var preserved:Object;
		public var cooked:Boolean;
		public var cooker:Object;
		private var _ref_id:uint;
		public var invoker:Object; // Broker
		
		
		private var unpersistable_atom: String;
		
		
		public function _Jellier(taster:Object, persistentStore:Object, invoker:Object)
		{
			//Initialize.
			
	        this.taster = taster;
	        //# `preserved' is a dict of previously seen instances.
	        this.preserved = {};
	        //# `cooked' is a dict of previously backreferenced instances to their `ref' lists.
	        this.cooked = {};
	        this.cooker = {};
	        this._ref_id = 1;
	        this.persistentStore = persistentStore;
	        this.invoker = invoker;
		}
		
	    private function _cook(object:Object):void
	    {
	        /*
	        (internal)
	
	        backreference an object.
	
	        Notes on this method for the hapless future maintainer: If I've already
	        gone through the prepare/preserve cycle on the specified object (it is
	        being referenced after the serializer is \"done with\" it, e.g. this
	        reference is NOT circular), the copy-in-place of aList is relevant,
	        since the list being modified is the actual, pre-existing jelly
	        expression that was returned for that object. If not, it's technically
	        superfluous, since the value in this.preserved didn't need to be set,
	        but the invariant that this.preserved[id(object)] is a list is
	        convenient because that means we don't have to test and create it or
	        not create it here, creating fewer code-paths.  that's why
	        this.preserved is always set to a list.
	
	        Sorry that this code is so hard to follow, but Python objects are
	        tricky to persist correctly. -glyph
	        */
			
			/*
	        aList = this.preserved[id(object)]
	        newList = copy.copy(aList)
	        //# make a new reference ID
	        refid = this._ref_id
	        this._ref_id = this._ref_id + 1
	        //# replace the old list in-place, so that we don't have to track the
	        //# previous reference to it.
	        aList[:] = [reference_atom, refid, newList]
	        this.cooked[id(object)] = [dereference_atom, refid]
	        return aList
	        */
	    }
	
	    public function prepare(object:Object):Array
	    {
	        /*
	        (internal)
	        create a list for persisting an object to.  this will allow
	        backreferences to be made internal to the object. (circular
	        references).
	
	        The reason this needs to happen is that we don't generate an ID for
	        every object, so we won't necessarily know which ID the object will
	        have in the future.  When it is 'cooked' ( see _cook ), it will be
	        assigned an ID, and the temporary placeholder list created here will be
	        modified in-place to create an expression that gives this object an ID:
	        [reference id# [object-jelly]].
	        */
	
	        //# create a placeholder list to be preserved
	        this.preserved[Jelly.id(object)] = [];
	        //# keep a reference to this object around, so it doesn't disappear!
	        //# (This isn't always necessary, but for cases where the objects are
	        //# dynamically generated by __getstate__ or getStateToCopyFor calls, it
	        //# is; id() will return the same value for a different object if it gets
	        //# garbage collected.  This may be optimized later.)
	        this.cooker[Jelly.id(object)] = object;
	        return [];
	    }
	
	    private function preserve(object:Object, sexp:Array):Array
	    {
	        /*
	        (internal)
	        mark an object's persistent list for later referral
	        */
	        //#if I've been cooked in the meanwhile,
	        
	        /*
	        if this.cooked.has_key(id(object)):
	            //# replace the placeholder empty list with the real one
	            this.preserved[id(object)][2] = sexp
	            //# but give this one back.
	            sexp = this.preserved[id(object)]
	        else:
	            this.preserved[id(object)] = sexp
	        return sexp
	        */
	        // FOR NOW
	        return sexp;
	    }
	
	    
	
	    private function _checkMutable(obj:Object):Object
	    {
	        /*
	        objId = id(obj)
	        if this.cooked.has_key(objId):
	            return this.cooked[objId]
	        if this.preserved.has_key(objId):
	            this._cook(obj)
	            return this.cooked[objId]
	        */
	        
	        // just return false for now until I can figure this out.
	        return false;
	    }
	
	    public function jelly(obj:*):*
	    {
	        //trace("jelly - "+obj);
	        /*
	        if(obj is ByteArray) 
	        {
	        	//trace("Jellying ByteArray");
	        	return obj;
	        }
	        */
	        var preRef:Object;
	        
	        if (obj is Jellyable)
	        {
	            preRef = this._checkMutable(obj);
	            if (preRef)
	            {
	                return preRef;
	            }
	            var ret:* = obj.jellyFor(this);
	            //trace("jellying Jellyable - "+ret);
	            return ret;
	        }
	        //objType = type(obj)
	        var objType:* = obj;
	        //if (this.taster.isTypeAllowed(qual(objType)))
	        if (this.taster.isTypeAllowed(objType))
	        {
	            //# "Immutable" Types
	            //trace('# "Immutable" Types');
	            if ((objType is String) ||
	                (objType is int) ||
	                (objType is uint) ||
	                (objType is Number))
	            {
	                return obj;
	            }
	            else if (objType is Function)
	            {
	                return ["method",
	                        obj.im_func.__name__,
	                        this.jelly(obj.im_self),
	                        this.jelly(obj.im_class)]
	            }
	            /*
	            elif UnicodeType and objType is UnicodeType:
	                return ['unicode', obj.encode('UTF-8')]
	            */    
	            else if (objType == null)
	            {
	                return ['None'];
	            }
	            /*
	            else if (objType is Function)
	            {
	                var name = obj.__name__;
	                return ['function', str(pickle.whichmodule(obj, obj.__name__))
	                        + '.' +
	                        name];
	            }
	            else if (objType is ModuleType)
	            {
	                return ['module', obj.__name__];
	            }
	            */
	            else if (objType is Boolean)
	            {
	                return ['boolean', obj ? 'true' : 'false'];
	            }
	            /*
	            elif objType is datetime.datetime:
	                if obj.tzinfo:
	                    raise NotImplementedError, "Currently can't jelly datetime objects with tzinfo"
	                return ['datetime', '%s %s %s %s %s %s %s' % (obj.year, obj.month, obj.day, obj.hour, obj.minute, obj.second, obj.microsecond)]
	            elif objType is datetime.time:
	                if obj.tzinfo:
	                    raise NotImplementedError, "Currently can't jelly datetime objects with tzinfo"
	                return ['time', '%s %s %s %s' % (obj.hour, obj.minute, obj.second, obj.microsecond)]
	            elif objType is datetime.date:
	                return ['date', '%s %s %s' % (obj.year, obj.month, obj.day)]
	            elif objType is datetime.timedelta:
	                return ['timedelta', '%s %s %s' % (obj.days, obj.seconds, obj.microseconds)]
	            elif objType is ClassType or issubclass(objType, type):
	                return ['class', qual(obj)]
	            */
	            else
	            {
	                preRef = this._checkMutable(obj);
	                if (preRef)
	                    return preRef
	                
	                //# "Mutable" Types
	                //trace('# "Mutable" Types');
	                var sxp:Array = this.prepare(obj);
	                
	                /*
	                if objType is ListType:
	                    sxp.append(list_atom)
	                    for item in obj:
	                        sxp.append(this.jelly(item))
	                elif objType is TupleType:
	                    sxp.append(tuple_atom)
	                    for item in obj:
	                        sxp.append(this.jelly(item))
	                elif objType in DictTypes:
	                    sxp.append(dictionary_atom)
	                    for key, val in obj.items():
	                        sxp.append([this.jelly(key), this.jelly(val)])
	                */
	                if(obj is PyTuple)
			        {
			        	sxp.push('tuple');
			        	var _fn:Function = function(item:*):*{sxp.push(jelly(item))}
			        	PyTuple(obj).forEach(_fn);
		
			        }
			        else if(obj is Object)
			        {
			        	sxp.push('dictionary');
			        	for (var i:String in obj)
				        {
				        	//trace(i+" = "+obj[i]);
				        	sxp.push(this.jelly(obj[i]))
				        }
			        }
	                
	                /*        
	                else:
	                    className = qual(obj.__class__)
	                    persistent = None
	                    if this.persistentStore:
	                        persistent = this.persistentStore(obj, this)
	                    if persistent is not None:
	                        sxp.append(persistent_atom)
	                        sxp.append(persistent)
	                    elif this.taster.isClassAllowed(obj.__class__):
	                        sxp.append(className)
	                        if hasattr(obj, "__getstate__"):
	                            state = obj.__getstate__()
	                        else:
	                            state = obj.__dict__
	                        sxp.append(this.jelly(state))
	                    else:
	                        this.unpersistable(
	                            "instance of class %s deemed insecure" %
	                            qual(obj.__class__), sxp)
	                */ 
	                
	                //trace("jellied an sxp : "+sxp);           
	                return this.preserve(obj, sxp);
	            }
	        }
	        else
	        {
	            /*
	            if (objType is InstanceType)
	            {
	                raise InsecureJelly("Class not allowed for instance: %s %s" %
	                                    (obj.__class__, obj))
	            }
	            raise InsecureJelly("Type not allowed for object: %s %s" %
	                                (objType, obj))
	            */                    
	        }
                   
	    }
	
	    private function unpersistable(reason:Object, sxp:Array=null):Array
	    {
	        /*
	        (internal)
	        Returns an sexp: (unpersistable "reason").  Utility method for making
	        note that a particular object could not be serialized.
	        */
	        if(sxp is null)
	        {
	            sxp = []
	        }
	        sxp.append(unpersistable_atom)
	        sxp.append(reason)
	        return sxp
	    }

	}
}